package uk.co.hephistocles.blobGame;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Vector;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.actors.Label;

public class Level {
	
	private static final int WINRADIUS = 50;
	
    private Game			dad;
	private Player 			player;
	private Vector2			winSpot;
	private Character		background;
	private PathFollower	blueStar;
	private Vector<GField> 	fields;
	private Vector<Line> 	level;
	private Vector<Line>	dangers;
	private int				offY;
	private int				offX;
	private double 			offAngle;
	private int				lvlNum;
	private int				worldNum;
	private int				roomNum;
	private int				roomTot;
	private long			lvlTime;
	private long			fastTime;
	private int				goodScore;
	private String[] 		lvlData;
	private	Character		blueSprite;
	private	Character		greenSprite;
	private GreenStar		greenStar;
	private	Character		coinSprite;
	private Collectibles	collectibles;
	public static int		score;
	public static float	fontScale;
	public static BitmapFont scoreFont = new BitmapFont(Gdx.files.internal("res/score.fnt"), false);
	public static final float scoreFontRatio = 4;

	public static float multiplier;

	//tol is the number of frames required in contact with the wall for the wall to be considered touching
	private final int tol = 5;
	private int wallState =0;
	
	//HUD STUFF
	private Dialog			hud;
	private Label			scoreLabel;
	
	//DEBUG STUFF
	private Pixmap			pixmap;
    private Texture			texture;
    private SpriteBatch 	batch;
    private TextureRegion 	region;
    
    public Level(Game parent) {
    	this.dad = parent;
    }
    
    public void initLevel(int worldNum, int lvl) {
	//world here corresponds to the number of the world OUT OF THOSE AVAILABLE, not globally.
    	if (dad.debug) {
			pixmap = new Pixmap(480,320, Pixmap.Format.RGBA4444);
			texture = new Texture (1024, 1024, Pixmap.Format.RGBA8888); //Pixmap.Format.RGBA8888);   
			texture.setFilter (Texture.TextureFilter.Nearest, Texture.TextureFilter.Linear);
			texture.setWrap   (Texture.TextureWrap.ClampToEdge, Texture.TextureWrap.ClampToEdge);
			 
			pixmap.setColor(1.0f, 1.0f, 1.0f, 1.0f);    // White
		}

		Game.currLevel = lvl;
		lvlNum = lvl;
		
		score = 0;
    	
		//World Name and Num refer to the global world data
		this.worldNum = worldNum;
		
		lvlData = Game.EXTERNAL.child(worldNum+"/level" + lvl + "/lvlData").readString().split(",");
		//one line per room, plus one line for score required, and one indicating where the green star is 
		roomTot = lvlData.length-2;
		
		player = new Player(0,0,Game.fps, this);
		winSpot = new Vector2();

		blueSprite= new Character(21, 28, 56, 7,Gdx.files.internal("res/bluestar.png"));
		greenSprite= new Character(21, 28, 56, 7,Gdx.files.internal("res/greenstar.png"));
		coinSprite = new Character(20, 32, 32, 5, Gdx.files.internal("res/coin.png"));

		collectibles = new Collectibles();
		
		hud = new Dialog();
		scoreLabel = hud.addLabel("scoreLabel", "Score: 0", scoreFont, Color.GREEN);
        scoreLabel.y = 5;
        
	    lvlTime = 0;
	    fastTime = 0;
	    try {
	    	goodScore = (int)Integer.parseInt(lvlData[0]);
	    } catch (NumberFormatException e) {
	    	System.out.println("Could not parse int for level time: " + lvlData[0]);
	    }
	    makeRoom(lvl,1);
    }

	void makeRoom(int lvl, int num) {
		
		roomNum = num;
		int i;
		
		//Initialise player in correct position
		String[] spots = Game.EXTERNAL.child(worldNum+"/level" + lvl + "/spots" + num + ".ai").readString().split("\r");
		String[] playerPos = spots[0].split(" ");
		player.pos.x = Float.parseFloat(playerPos[0]);
		player.pos.y = Float.parseFloat(playerPos[1]);
		player.reset();
		
		//initialise collectibles
		collectibles.destroy();
		String[] winSpotString;
		

		//Initialise the background of the level
		background = new Character(Game.EXTERNAL.child(worldNum+"/level" + lvl + "/background" + num +".png"));  
		
		//if this room contains the greenStar
		if ((int)Float.parseFloat(lvlData[lvlData.length-1]) == num) {
			//add coins for the 2nd to pre-penultimate spots
			for (i=1; i<spots.length-3; i++){ 
				collectibles.add(new Coin(
						 	(int)Float.parseFloat(spots[i].split(" ")[0]),
						 	(int)Float.parseFloat(spots[i].split(" ")[1]),
							25,coinSprite));
			}
			//add a green star at the penultimate spot
			greenStar = new GreenStar(
				 	(int)Float.parseFloat(spots[spots.length-1].split(" ")[0]),
				 	(int)Float.parseFloat(spots[spots.length-1].split(" ")[1]),
					30,greenSprite);
			collectibles.add(greenStar);


			//Initialise winning spot
			winSpotString = spots[spots.length-2].split(" ");
		} else {
			//add coins for the 2nd to penultimate spots
			for (i=1; i<spots.length-2; i++){ 
				collectibles.add(new Coin(
						 	(int)Float.parseFloat(spots[i].split(" ")[0]),
						 	(int)Float.parseFloat(spots[i].split(" ")[1]),
							25,coinSprite));
			}

			//Initialise winning spot
			winSpotString = spots[spots.length-1].split(" ");
		}
		
		
		winSpot.x = Float.parseFloat(winSpotString[0]);
		winSpot.y = Float.parseFloat(winSpotString[1]);



		//Initialise the walls of the level
		BezierPath walls = new BezierPath();
		walls.init(Game.EXTERNAL.child(worldNum+"/level" + lvl + "/walls" + num + ".ai"));
		level = walls.toLines();
		
		if (Game.debug) {
			for (i=0; i<level.size();i++) {
				pixmap.drawLine(level.get(i).x1,320-level.get(i).y1,level.get(i).x2,320-level.get(i).y2);
			}
		}
			

		//Initialise the spikes of the level
		BezierPath spikes = new BezierPath();
		spikes.init(Game.EXTERNAL.child(worldNum+"/level" + lvl + "/spikes" + num + ".ai"));
		dangers = spikes.toLines();
		
		
		//Initialise the racing star's path
		BezierPath curve = new BezierPath();
		curve.init(Game.EXTERNAL.child(worldNum+"/level" + lvl + "/bezier" + num + ".ai"));
				
		blueStar = new PathFollower(blueSprite, curve, 
				(int)Float.parseFloat(lvlData[num].split(" ")[0]));
		blueStar.update((int) (lvlTime -fastTime));
		fastTime += Float.parseFloat(lvlData[num].split(" ")[0]);
		blueStar.setOrigin(11f,43f);
		
		//Initialise the fixed-gravity fields
		fields = new Vector<GField>();
		
		//read the file containing field data
		FileHandle fieldsHandle = Game.EXTERNAL.child(worldNum+"/level" + lvl + "/fields"+num+".ai");
		String[] fieldsFile = fieldsHandle.readString().split("\r");
		int count = fieldsFile.length;
		
		//this boolean is used to determine whether we're reading direction or outline
		boolean newField = true;
		float fieldDir = 0;
		LinkedList<Vector3> pts = new LinkedList<Vector3>();;
		Vector<Bezier> fieldOutline  = new Vector<Bezier>();
		
		for (i=1; i<count; i++) {
			System.out.println("Looking at line " + (1+i) + ": " + fieldsFile[i]);
			if (newField) {
				//each field definition begins with two coordinates showing direction of the field
				String[] first = fieldsFile[i-1].split(" ");
				String[] last = fieldsFile[i].split(" ");
				fieldDir = -(float)Math.atan2(Float.parseFloat(last[1])-Float.parseFloat(first[1]),
						Float.parseFloat(last[0])-Float.parseFloat(first[0]));
				fieldOutline = new Vector<Bezier>();

				newField = false;
				i+=2;
			} else {
				if (fieldsFile[i].isEmpty()) {
					System.out.println("IT IS EMPTY!");
					//if the line is empty, then this field is finished, move onto the next.
					BezierPath p = new BezierPath();
					p.init(fieldOutline);
					GField e = new GField(p,fieldDir);
					fields.add(e);
					newField = true;
					i+=1;
				} else {
					//if we're not starting a new field, read the Bezier outline as normal
					String[] o = fieldsFile[i-1].split(" ");
					String[] c = fieldsFile[i].split(" ");
					if (fieldsFile[i].length() > 3) {
						pts.clear();
						//the first control point of a Bezier is the last of the previous, in a spline
						pts.add(new Vector3(Float.parseFloat(o[o.length-3]),
								Float.parseFloat(o[o.length-2]),0));
						for (int j=0; j<c.length/2; j++) {
							pts.addLast(new Vector3(Float.parseFloat(c[2*j]),Float.parseFloat(c[2*j+1]),0));
						}
						fieldOutline.add(new Bezier((LinkedList<Vector3>) pts.clone()));
					} else {
						i++;
					}
				}
			}
		}

		BezierPath p = new BezierPath();
		p.init(fieldOutline);
		GField e = new GField(p,fieldDir);
		fields.add(e);
		
				
		//draw lines along surfaces if debug mode is enabled
		if (Game.debug) {
			texture.draw (pixmap, 0,0);
			region = new TextureRegion(texture, 0, 0, 480,320);
			batch = new SpriteBatch();
	 		pixmap.dispose();
		}
		
		fontScale = 0.2f;
        scoreLabel.cache.getFont().setScale( fontScale);
        scoreLabel.x = (Gdx.graphics.getWidth() - scoreLabel.width)/2;
		dad.state = dad.RUNNING;
	}
	
	public void update(int dt) {
		collisionCheck();
        deathCheck();
        winCheck();
        blueStar.update(dt);
        collectibles.update(player.pos.x, player.pos.y);
        lvlTime += dt;
        
		offAngle = 0;
		if ((Game.touchControl) ||(Game.keyBoardControl)) {
			offAngle = player.weightAngle - Math.PI/2;
		}
        offX = (int) player.pos.x;
        offY = (int) player.pos.y;
        if (Game.touchControl || Game.keyBoardControl) {
	        if (offX<Gdx.graphics.getHeight()/2){
	        	offX=Gdx.graphics.getHeight()/2;
			} else if (offX> background.width-Gdx.graphics.getHeight()/2) {
				offX = background.width-Gdx.graphics.getHeight()/2;
			}
	        if (offY<Gdx.graphics.getHeight()/2){
	        	offY=Gdx.graphics.getHeight()/2;
			} else if (offY> background.height-Gdx.graphics.getHeight()/2) {
				offY = background.height-Gdx.graphics.getHeight()/2;
			}
        } else {
	        if (offX<Gdx.graphics.getWidth()/2){
	        	offX=Gdx.graphics.getWidth()/2;
			} else if (offX> background.width-Gdx.graphics.getWidth()/2) {
				offX = background.width-Gdx.graphics.getWidth()/2;
			}
	        if (offY<Gdx.graphics.getHeight()/2){
	        	offY=Gdx.graphics.getHeight()/2;
			} else if (offY> background.height-Gdx.graphics.getHeight()/2) {
				offY = background.height-Gdx.graphics.getHeight()/2;
			}
        }
        scoreLabel.setText("Score: " + score);
    	if (fontScale > 1){
        	fontScale=1f;
        	scoreLabel.cache.getFont().setScale(1.05f);
        } else {
        	scoreLabel.cache.getFont().setScale((scoreLabel.cache.getFont().getScaleX() + fontScale)/2);
        }
        scoreLabel.x = (Gdx.graphics.getWidth() - scoreLabel.width)/2;

		hud.update(dt);
	}

	public void draw() {
        background.draw(0,0);
        blueStar.draw();
		collectibles.draw();
		player.draw();
		Drawer.draw(offX, offY, (float) (180f*offAngle/Math.PI));
		hud.draw();
        if (Game.debug) {
			batch.begin();
	        batch.draw (region, Gdx.graphics.getWidth(),  Gdx.graphics.getHeight());
	        batch.end();
        }
	}
	
	private void collisionCheck() {
		Vector<Double> normals = new Vector<Double>();
		Vector<Boolean> collisions = new Vector<Boolean>();
		int i = 0;
		double ax, ay, bx, by, cx, cy, dirx, diry, difx, dify, dx, dy;
		cx = player.pos.x;
		cy = player.pos.y;
		for (i=0; i<level.size(); i++) {
			ax = level.get(i).x1;
			ay = level.get(i).y1;
			bx = level.get(i).x2;
			by = level.get(i).y2;

			if ((cx>Math.min(ax,bx)-player.radius) && (cx<Math.max(ax,bx)+player.radius) &&
					(cy>Math.min(ay,by)-player.radius) && (cy<Math.max(ay,by)+player.radius)) {
				// dir = b-a
				dirx = bx-ax;
				diry = by-ay;
				// diff = c - a;
				difx = cx-ax;
				dify = cy-ay;
				
				double t = (difx*dirx + dify*diry) / (dirx*dirx + diry*diry);
				if (t < 0.0)
					t = 0.0;
				if (t > 1.0)
					t = 1.0;
				
				//dir (new) = a + t*dir
				dirx *= t;
				diry *= t;
				dirx += ax;
				diry += ay;
				
				//d = c-dir(new)
				dx = cx - dirx;
				dy = cy - diry;
	
				//if actual collision
				if (dx*dx + dy*dy <= (player.radius)*(player.radius)) {
					normals.add(level.get(i).normal);
					collisions.add(true);
				}
			}
		}
		if (collisions.size() > 0) {
			touchWall();
		} else {
			leaveWall();
		}
		boolean inField = false;
		
        //check fields for intersection
        for (i =0; i<fields.size(); i++) {
        	System.out.println("Checking..");
        	if (fields.get(i).contains(player.pos.x, player.pos.y)) {
        		inField = true;
        		System.out.println("AM IN UR FIELD " + fields.get(i));
        		player.update(normals, collisions, wallState, fields.get(i).dir);
        		break;
        	}
        }
        if (!inField) {
        	player.update(normals, collisions, wallState);
        }
	}
	
	private void touchWall() {
		wallState++;
		if (wallState >tol) {
			wallState = tol;
		}
	}
	
	private void leaveWall() {
		wallState--;		
		if (wallState <-tol) {
			wallState = -tol;
		}
	}
	
	private void deathCheck() {
		int i = 0;
		double ax, ay, bx, by, cx, cy, dirx, diry, difx, dify, dx, dy;
		cx = player.pos.x;
		cy = player.pos.y;
		for (i=0; i<dangers.size(); i++) {
			ax = dangers.get(i).x1;
			ay = dangers.get(i).y1;
			bx = dangers.get(i).x2;
			by = dangers.get(i).y2;
			
			//initial bounding box check just to save on complex vector computations
			if ((cx>Math.min(ax,bx)-player.width/2) && (cx<Math.max(ax,bx)+player.width/2) &&
					(cy>Math.min(ay,by)-player.height/2) && (cy<Math.max(ay,by)+player.height/2)) {
				// dir = is a vector from B -> A
				dirx = bx-ax;
				diry = by-ay;
				// diff is a vector from C -> A
				difx = cx-ax;
				dify = cy-ay;
				
				//(difx*dirx + dify*diry) is dir DOT diff i.e. length base of a right-angled triangle with peak at C (draw diagram)
				double t = (difx*dirx + dify*diry) / (dirx*dirx + diry*diry);
				if (t < 0.0)
					t = 0.0;
				if (t > 1.0)
					t = 1.0;
				
				//dir (new) = a + t*dir
				dirx *= t;
				diry *= t;
				dirx += ax;
				diry += ay;
				
				//d = c-dir(new)
				dx = cx - dirx;
				dy = cy - diry;
	
				if(dx*dx + dy*dy <= (player.radius)*(player.radius)) {
		        	scoreLabel.cache.getFont().setScale(1f);
		        	player.die();
		        	HashMap<String, String> eP = new HashMap<String,String>();
		        	eP.put("Level", worldNum + "." + lvlNum);
		        	eP.put("Time (so far)", lvlTime +"/" + fastTime);
		        	eP.put("Score (so far)", score +"/" + goodScore);
		        	eP.put("Green Star (so far)", ""+greenStar.isCollected());
		        	eP.put("danger number", ""+i);
					dad.die(eP);
					return;
				}
			}
		}
	}
	
	private void winCheck() {
	
		Vector2 c = new Vector2(player.pos.x,player.pos.y);
		if (c.dst(winSpot) < WINRADIUS) {
			roomNum++;
			if (roomNum > roomTot) {
				dispose();
	        	scoreLabel.cache.getFont().setScale(1f);
	        	HashMap<String, String> eP = new HashMap<String,String>();
	        	eP.put("Level", worldNum + "." + lvlNum);
	        	eP.put("Time", lvlTime +"/" + fastTime);
	        	eP.put("Score", score +"/" + goodScore);
	        	eP.put("Green Star", ""+greenStar.isCollected());
				dad.win(lvlTime< fastTime,greenStar.isCollected(), score>goodScore, score,eP);
			} else {
				makeRoom(lvlNum,roomNum);
			}
		}
	}
	
	public void dispose() {
		Drawer.dispose();
	}

	public void rotateChildren(float a) {
		//player.setRotation(a);
		//blueStar.setRotation(a);
		
	}
}
